package com.disv.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.dingtalkcontact_1_0.models.GetOrgAuthInfoHeaders;
import com.aliyun.dingtalkcontact_1_0.models.GetOrgAuthInfoRequest;
import com.aliyun.dingtalkcontact_1_0.models.GetOrgAuthInfoResponse;
import com.aliyun.dingtalkoauth2_1_0.Client;
import com.aliyun.dingtalkoauth2_1_0.models.*;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiServiceGetCorpTokenRequest;
import com.dingtalk.api.request.OapiV2DepartmentGetRequest;
import com.dingtalk.api.request.OapiV2DepartmentListsubRequest;
import com.dingtalk.api.request.OapiV2UserListRequest;
import com.dingtalk.api.response.OapiServiceGetCorpTokenResponse;
import com.dingtalk.api.response.OapiV2DepartmentGetResponse;
import com.dingtalk.api.response.OapiV2DepartmentListsubResponse;
import com.dingtalk.api.response.OapiV2UserListResponse;
import com.disv.domain.DdDeptInfo;
import com.disv.domain.DdDeptUserInfo;
import com.disv.domain.DdDeptUserRe;
import com.disv.service.DingOperateService;
import com.disv.support.config.AppRespCode;
import com.disv.support.config.CacheManagerUtil;
import com.disv.support.config.Constant;
import com.disv.support.exception.BusinessException;
import com.taobao.api.ApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 钉钉服务
 *
 * @author hyt
 * @date 2023/06/09
 */
@Service
@Slf4j
public class DingOperateServiceImpl implements DingOperateService {
    @Value("${dd.app.suiteKey}")
    private String dingAppSuiteKey;
    @Value("${dd.app.suiteSecret}")
    private String dingAppsuiteSecret;

    /**
     * 创建新SDK客户端
     *
     * @return {@link Client}
     * @throws Exception 异常
     */
    public static Client createNewOauthClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new Client(config);
    }

    /**
     * 创建新SDK客户端
     *
     * @return {@link com.aliyun.dingtalkcontact_1_0.Client}
     * @throws Exception 异常
     */
    public static com.aliyun.dingtalkcontact_1_0.Client createNewContactClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new com.aliyun.dingtalkcontact_1_0.Client(config);
    }

    /**
     * 产品服务商可通过此接口获取授权企业的accessToken。
     * 当第三方企业应用调用服务端API获取应用资源时，使用获取的accessToken进行身份验证。
     *
     * @return
     */
    @Override
    public GetCorpAccessTokenResponse getCorpAccessToken(String authCorpId) throws Exception {
        Client client = this.createNewOauthClient();
        GetCorpAccessTokenRequest getCorpAccessTokenRequest = new GetCorpAccessTokenRequest()
                .setSuiteKey(this.dingAppSuiteKey)
                .setSuiteSecret(this.dingAppsuiteSecret)
                .setAuthCorpId(authCorpId)
                .setSuiteTicket((String) CacheManagerUtil.getValue(this.dingAppSuiteKey));
        try {
            GetCorpAccessTokenResponse getCorpAccessTokenResponse = client.getCorpAccessToken(getCorpAccessTokenRequest);
            CacheManagerUtil.setCacheValueEx(dingAppSuiteKey + Constant.DING_CROP_ACCESS_TOKEN + authCorpId
                    , getCorpAccessTokenResponse.getBody().getAccessToken()
                    , getCorpAccessTokenResponse.getBody().getExpireIn());
            return client.getCorpAccessToken(getCorpAccessTokenRequest);
        } catch (TeaException err) {
            log.error("[钉钉]获取access_token失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_ACCESS_TOKEN_FAIL);

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取access_token失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_ACCESS_TOKEN_FAIL);
        }
    }

    /**
     * 第三方企业可调用本接口获取企业开通应用后的授权信息。
     *
     * @return
     */
    @Override
    public GetAuthInfoResponse getAuthInfo(String authCorpId) throws Exception {
        // 获取access token, 先从缓存获取，获取失败在调用钉钉接口获取
        String dingAccessToken = (String) CacheManagerUtil.getValue(dingAppSuiteKey + Constant.DING_SUITE_ACCESS_TOKEN);
        if (StrUtil.isEmpty(dingAccessToken)) {
            GetSuiteAccessTokenResponse getSuiteAccessTokenResponse = this.getSuiteAccessToken();
            dingAccessToken = getSuiteAccessTokenResponse.getBody().getAccessToken();
        }

        Client client = this.createNewOauthClient();
        GetAuthInfoHeaders getAuthInfoHeaders = new GetAuthInfoHeaders();
        getAuthInfoHeaders.xAcsDingtalkAccessToken = dingAccessToken;
        GetAuthInfoRequest getAuthInfoRequest = new GetAuthInfoRequest()
                .setAuthCorpId(authCorpId);
        try {
            return client.getAuthInfoWithOptions(getAuthInfoRequest, getAuthInfoHeaders, new RuntimeOptions());
        } catch (TeaException err) {
            log.error("[钉钉]获取authInfo失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_AUTH_INFO_FAIL);

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取authInfo失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_AUTH_INFO_FAIL);
        }
    }


    /**
     * 调用本接口获取第三方企业应用的suiteAccessToken。
     * 该suiteAccessToken主要用于获取第三方企业应用的信息
     *
     * @return
     * @throws Exception 异常
     */
    @Override
    public GetSuiteAccessTokenResponse getSuiteAccessToken() throws Exception {
        Client client = this.createNewOauthClient();
        GetSuiteAccessTokenRequest getSuiteAccessTokenRequest = new GetSuiteAccessTokenRequest()
                .setSuiteKey(this.dingAppSuiteKey)
                .setSuiteSecret(this.dingAppsuiteSecret)
                .setSuiteTicket((String) CacheManagerUtil.getValue(this.dingAppSuiteKey));
        try {
            return client.getSuiteAccessToken(getSuiteAccessTokenRequest);
        } catch (TeaException err) {
            log.error("[钉钉]获取suiteAccessToken失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_SUITE_ACCESS_TOKEN_FAIL);
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取suiteAccessToken失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_SUITE_ACCESS_TOKEN_FAIL);
        }
    }

    /**
     * 调用本接口，获取企业认证信息
     *
     * @param authCorpId auth . id
     * @return
     */
    @Override
    public GetOrgAuthInfoResponse getOrgAuthInfos(String authCorpId) throws Exception {
        System.setProperty("java.net.useSystemProxies", "true");

        // 获取access token, 先从缓存获取，获取失败在调用钉钉接口获取
        String dingAccessToken = (String) CacheManagerUtil.getValue(dingAppSuiteKey + Constant.DING_CROP_ACCESS_TOKEN + authCorpId);
        if (StrUtil.isEmpty(dingAccessToken)) {
            GetCorpAccessTokenResponse getCorpAccessTokenResponse = this.getCorpAccessToken(authCorpId);
            dingAccessToken = getCorpAccessTokenResponse.getBody().getAccessToken();
        }

        com.aliyun.dingtalkcontact_1_0.Client client = this.createNewContactClient();
        GetOrgAuthInfoHeaders getOrgAuthInfoHeaders = new GetOrgAuthInfoHeaders();
        getOrgAuthInfoHeaders.xAcsDingtalkAccessToken = dingAccessToken;
        GetOrgAuthInfoRequest getOrgAuthInfoRequest = new GetOrgAuthInfoRequest()
                .setTargetCorpId(authCorpId);
        try {
            return client.getOrgAuthInfoWithOptions(getOrgAuthInfoRequest, getOrgAuthInfoHeaders, new RuntimeOptions());
        } catch (TeaException err) {
            log.error("[钉钉]获取suiteAccessToken失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_ORG_AUTH_INFO_FAIL);
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取suiteAccessToken失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_ORG_AUTH_INFO_FAIL);
        }
    }

    /**
     * 获取部门列表
     *
     * @param authCorpId auth . id
     * @param dept_id    部门id
     * @return {@link OapiV2DepartmentListsubResponse}
     * @throws Exception 异常
     */
    @Override
    public OapiV2DepartmentListsubResponse getDepartmentListsub(String authCorpId, Long dept_id) {
        log.info("获取部分信息----authCorpId:【{}】,dept_id【{}】", authCorpId, dept_id);
        // 获取access token, 先从缓存获取，获取失败在调用钉钉接口获取
        String dingAccessToken = (String) CacheManagerUtil.getValue(dingAppSuiteKey + Constant.DING_CROP_TOKEN + authCorpId);
        if (StrUtil.isEmpty(dingAccessToken)) {
            OapiServiceGetCorpTokenResponse getCorpTokenResponse = this.getCorpToken(authCorpId);
            dingAccessToken = getCorpTokenResponse.getAccessToken();
        }

        DingTalkClient client = new DefaultDingTalkClient(Constant.GET_DEPARTMENT_LISTSUB_URL);
        OapiV2DepartmentListsubRequest req = new OapiV2DepartmentListsubRequest();
        req.setDeptId(dept_id);
        try {
            return client.execute(req, dingAccessToken);
        } catch (ApiException err) {
            log.error("[钉钉]获取departmentListsub失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_DEPARTMENT_LIST_SUB_FAIL);
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取departmentListsub失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_DEPARTMENT_LIST_SUB_FAIL);
        }
    }

    /**
     * 服务商获取第三方应用授权企业的access_token
     *
     * @param authCorpId auth . id
     * @return
     */
    @Override
    public OapiServiceGetCorpTokenResponse getCorpToken(String authCorpId) {
        DefaultDingTalkClient client = new DefaultDingTalkClient(Constant.GET_CROP_TOKEN_URL);
        OapiServiceGetCorpTokenRequest req = new OapiServiceGetCorpTokenRequest();
        req.setAuthCorpid(authCorpId);
        try {
            OapiServiceGetCorpTokenResponse execute = client.execute(req, this.dingAppSuiteKey, this.dingAppsuiteSecret
                    , (String) CacheManagerUtil.getValue(this.dingAppSuiteKey));
            CacheManagerUtil.setCacheValueEx(dingAppSuiteKey + Constant.DING_CROP_TOKEN + authCorpId
                    , execute.getAccessToken(), execute.getExpiresIn());
            return execute;
        } catch (ApiException err) {
            log.error("[钉钉]获取getCorpToken失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_CROP_TOKEN_FAIL);
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取getCorpToken失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_CROP_TOKEN_FAIL);
        }
    }

    /**
     * 分页查询获取部门用户详情
     *
     * @param authCorpId auth . id
     * @param dept_id    部门id
     * @param cursor     光标
     * @param size       大小
     * @return {@link OapiV2UserListResponse}
     */
    @Override
    public OapiV2UserListResponse getDepartmentUserList(String authCorpId, Long dept_id, long cursor, long size) {
        log.info("获取部分信息----authCorpId:【{}】,dept_id【{}】,cursor【{}】, size【{}】", authCorpId, dept_id, cursor, size);
        // 获取access token, 先从缓存获取，获取失败在调用钉钉接口获取
        String dingAccessToken = (String) CacheManagerUtil.getValue(dingAppSuiteKey + Constant.DING_CROP_TOKEN + authCorpId);
        if (StrUtil.isEmpty(dingAccessToken)) {
            OapiServiceGetCorpTokenResponse getCorpTokenResponse = this.getCorpToken(authCorpId);
            dingAccessToken = getCorpTokenResponse.getAccessToken();
        }

        DingTalkClient client = new DefaultDingTalkClient(Constant.GET_DEPARTMENT_USER_LIST_URL);
        OapiV2UserListRequest req = new OapiV2UserListRequest();
        req.setDeptId(dept_id);
        req.setCursor(cursor);
        req.setSize(size);
        req.setOrderField("custom");
        req.setContainAccessLimit(false);
        req.setLanguage("zh_CN");
        try {
            OapiV2UserListResponse execute = client.execute(req, dingAccessToken);
            return execute;
        } catch (ApiException err) {
            log.error("[钉钉]获取部门用户详情失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_DEPARTMENT_USER_LIST_FAIL);
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取部门用户详情失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_DEPARTMENT_USER_LIST_FAIL);
        }
    }

    /**
     * 获取钉钉所有部门列表，并存入部门表
     *
     * @param authCorpId auth . id
     * @param dept_id    部门id
     * @return {@link OapiV2DepartmentListsubResponse}
     */
    @Override
    public List<DdDeptInfo> departmentListsubAll(String authCorpId, long dept_id) {
        OapiV2DepartmentListsubResponse oapiV2UserListResponse = this.getDepartmentListsub(authCorpId, dept_id);
        List<OapiV2DepartmentListsubResponse.DeptBaseResponse> deptBaseResponses = oapiV2UserListResponse.getResult();
        List<DdDeptInfo> ddDeptInfos = new ArrayList<>();

        // 获取本部门信息
        OapiV2DepartmentGetResponse oapiV2DepartmentGetResponse = this.departmentDetails(authCorpId, dept_id);
        if (ObjectUtil.isNotEmpty(oapiV2DepartmentGetResponse.getResult())) {
            DdDeptInfo ddDeptInfo = BeanUtil.copyProperties(oapiV2DepartmentGetResponse.getResult(), DdDeptInfo.class);
            ddDeptInfo.setCorpId(authCorpId);
            ddDeptInfo.setCreateTime(new Date());
            ddDeptInfos.add(ddDeptInfo);
        }

        buildDdDeptInfos(deptBaseResponses, ddDeptInfos, authCorpId);
        return ddDeptInfos;
    }

    /**
     * 构建钉钉部门信息
     *
     * @param deptBaseResponses 部门基本反应
     * @param ddDeptInfos       dd部门信息
     * @param authCorpId        auth . id
     */
    private void buildDdDeptInfos(List<OapiV2DepartmentListsubResponse.DeptBaseResponse> deptBaseResponses, List<DdDeptInfo> ddDeptInfos
            , String authCorpId) {
        if (CollectionUtil.isNotEmpty(deptBaseResponses)) {
            deptBaseResponses.forEach(deptBaseResponse -> {
                DdDeptInfo ddDeptInfo = BeanUtil.copyProperties(deptBaseResponse, DdDeptInfo.class);
                ddDeptInfo.setCreateTime(new Date());
                ddDeptInfo.setCorpId(authCorpId);
                ddDeptInfos.add(ddDeptInfo);
                OapiV2DepartmentListsubResponse oapiV2UserListResponse = this.getDepartmentListsub(authCorpId, deptBaseResponse.getDeptId());
                List<OapiV2DepartmentListsubResponse.DeptBaseResponse> deptBaseResponseList = oapiV2UserListResponse.getResult();
                buildDdDeptInfos(deptBaseResponseList, ddDeptInfos, authCorpId);
            });
        }
    }

    /**
     * 通过部门获取所有用户信息，及部门与用户信息
     *
     * @param authCorpId auth . id
     * @return {@link OapiV2UserListResponse}
     */
    @Override
    @Transactional
    public OapiV2UserListResponse departmentUserListAll(String authCorpId) {
        List<DdDeptInfo> ddDeptInfos = this.departmentListsubAll(authCorpId, 1L);
        ddDeptInfos.forEach(ddDeptInfo -> {
            // 获取部分所有用户
            OapiV2UserListResponse oapiV2UserListResponse = this.getDepartmentUserList(authCorpId, Long.parseLong(ddDeptInfo.getDeptId()), 0L, 10L);
            OapiV2UserListResponse.PageResult result = oapiV2UserListResponse.getResult();
            pageDepartmentUserList(authCorpId, Long.parseLong(ddDeptInfo.getDeptId()), result);
        });
        return null;
    }

    /**
     * 获取部门所有用户信息
     *
     * @param authCorpId auth . id
     * @param dept_id    部门id
     * @param result     结果
     */
    private void pageDepartmentUserList(String authCorpId, long dept_id, OapiV2UserListResponse.PageResult result) {
        buildDeptUserList(authCorpId, dept_id, result);
        if (result.getHasMore()) {
            OapiV2UserListResponse oapiV2UserListResponse = this.getDepartmentUserList(authCorpId, dept_id, result.getNextCursor(), 10L);
            OapiV2UserListResponse.PageResult pageResult = oapiV2UserListResponse.getResult();
            pageDepartmentUserList(authCorpId, dept_id, pageResult);
        }
    }

    /**
     * 构建部门用户和关系表数据
     *
     * @param authCorpId auth . id
     * @param dept_id    部门id
     * @param result     结果
     */
    private void buildDeptUserList(String authCorpId, long dept_id, OapiV2UserListResponse.PageResult result) {
        List<DdDeptUserInfo> ddDeptUserInfos = new ArrayList<>();
        List<DdDeptUserRe> ddDeptUserRes = new ArrayList<>();
        result.getList().forEach(listUserResponse -> {
            DdDeptUserInfo ddDeptUserInfo = BeanUtil.copyProperties(listUserResponse, DdDeptUserInfo.class);
            ddDeptUserInfo.setUserId(listUserResponse.getUserid());
            ddDeptUserInfo.setUnionId(listUserResponse.getUnionid());
            ddDeptUserInfo.setCreateTime(new Date());
            ddDeptUserInfo.setCorpId(authCorpId);
            ddDeptUserInfos.add(ddDeptUserInfo);
            DdDeptUserRe ddDeptUserRe = new DdDeptUserRe();
            ddDeptUserRe.setUserId(ddDeptUserInfo.getUserId());
            ddDeptUserRe.setUnionId(ddDeptUserInfo.getUnionId());
            ddDeptUserRe.setCreateTime(new Date());
            ddDeptUserRe.setCorpId(authCorpId);
            ddDeptUserRe.setDeptId(String.valueOf(dept_id));
            ddDeptUserRes.add(ddDeptUserRe);
        });
    }

    @Override
    public OapiV2DepartmentGetResponse departmentDetails(String authCorpId, long dept_id) {
        log.info("获取部门详情信息----authCorpId:【{}】,dept_id【{}】", authCorpId, dept_id);
        // 获取access token, 先从缓存获取，获取失败在调用钉钉接口获取
        String dingAccessToken = (String) CacheManagerUtil.getValue(dingAppSuiteKey + Constant.DING_CROP_TOKEN + authCorpId);
        if (StrUtil.isEmpty(dingAccessToken)) {
            OapiServiceGetCorpTokenResponse getCorpTokenResponse = this.getCorpToken(authCorpId);
            dingAccessToken = getCorpTokenResponse.getAccessToken();
        }
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/department/get");
        OapiV2DepartmentGetRequest req = new OapiV2DepartmentGetRequest();
        req.setDeptId(dept_id);
        req.setLanguage("zh_CN");
        try {
            OapiV2DepartmentGetResponse rsp = client.execute(req, dingAccessToken);
            return rsp;
        } catch (ApiException err) {
            log.error("[钉钉]获取部门详情信息失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_DEPARTMENT_DETAILS_FAIL);
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            log.error("[钉钉]获取部门详情信息失败:", err);
            throw new BusinessException(AppRespCode.DING_DING_GET_DEPARTMENT_DETAILS_FAIL);
        }
    }
}
